package lk.icta.common.util;

/**
 * @author Kusal Samarakoon
 */

import lk.icta.common.dto.GeoPoint;
import lk.icta.core.dto.GeoParcelCodeDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class KmlWriter {

    private static final Logger logger = LoggerFactory.getLogger(KmlWriter.class);
    private long referenceNo;

    // Write KML
    public File writeKmlFile(File file) {
        File createFile = null;
        try {

            DocumentBuilderFactory docFactory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

            // root elements
            Document doc = docBuilder.newDocument();
            Element rootElement = doc.createElement("kml");
            doc.appendChild(rootElement);

            // placemark elements
            Element placemark = doc.createElement("Placemark");
            rootElement.appendChild(placemark);

            // name elements
            Element name = doc.createElement("name");
            name.appendChild(doc.createTextNode("Land"));
            placemark.appendChild(name);

            // Polygon elements
            Element polygon = doc.createElement("Polygon");
            placemark.appendChild(polygon);

            // extrude elements
            Element extrude = doc.createElement("extrude");
            extrude.appendChild(doc.createTextNode("1"));
            polygon.appendChild(extrude);

            // altitudeMode elements
            Element altitudeMode = doc.createElement("altitudeMode");
            altitudeMode.appendChild(doc.createTextNode("relativeToGround"));
            polygon.appendChild(altitudeMode);

            // outerBoundaryIs elements
            Element outerBoundaryIs = doc.createElement("outerBoundaryIs");
            polygon.appendChild(outerBoundaryIs);

            // LinearRing elements
            Element LinearRing = doc.createElement("LinearRing");
            outerBoundaryIs.appendChild(LinearRing);

            // coordinates elements
            Element coordinates = doc.createElement("coordinates");
            // ReadXMLFile readXMLFile = new ReadXMLFile();
            String resultCoordinates = getCoordinateValue(file);
            GeoParcelCodeDetail middlePoint = getMiddlePoint(resultCoordinates);

            String resultPoint = middlePoint.getLatitude() + "," + middlePoint.getLongitude() + "," +
                    middlePoint.getAltitude();

            if (resultCoordinates.length() > 0) {
                coordinates.appendChild(doc.createTextNode(resultCoordinates));

            }
            LinearRing.appendChild(coordinates);

            // Add a point
            // placemark elements
            Element placemark2 = doc.createElement("Placemark");
            rootElement.appendChild(placemark2);

            // name elements
            Element name2 = doc.createElement("name");
            name2.appendChild(doc.createTextNode("" + getReferenceNo()));
            placemark2.appendChild(name2);

            // description elements
            Element description = doc.createElement("description");
            description.appendChild(doc.createTextNode("Middle of the Land"));
            placemark2.appendChild(description);

            // Point elements
            Element Point = doc.createElement("Point");
            placemark2.appendChild(Point);

            // coordinates elements to point
            Element coordinatesPoint = doc.createElement("coordinates");
            //String resultPoint = getMiddlePoint(resultCoordinates);
            if (resultCoordinates.length() > 0) {
                coordinatesPoint.appendChild(doc.createTextNode(resultPoint));
            }

            Point.appendChild(coordinatesPoint);

            // referenceNo elements
            Element referenceNoEle = doc.createElement("referenceNo");
            if (getReferenceNo() != 0) {
                referenceNoEle.appendChild(doc.createTextNode("" + getReferenceNo()));
            }
            Point.appendChild(referenceNoEle);

            // write the content into kml file
            TransformerFactory transformerFactory = TransformerFactory
                    .newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(doc);
            createFile = file;
            StreamResult result = new StreamResult(createFile);

            transformer.transform(source, result);

            System.out.println("File saved!");

        } catch (ParserConfigurationException pce) {
            pce.printStackTrace();
        } catch (TransformerException tfe) {
            tfe.printStackTrace();
        }

        return createFile;
    }

    // Read KML
    public String getCoordinateValue(File fXmlFile) {
        String result = "";
        try {

            //File fXmlFile = new File(fileName);
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(fXmlFile);
            doc.getDocumentElement().normalize();


            NodeList nList = doc.getElementsByTagName("LinearRing");
            if (nList != null) {
                if (nList.getLength() == 0) {
                    nList = doc.getElementsByTagName("LineString");
                }

                for (int temp = 0; temp < nList.getLength(); temp++) {

                    Node nNode = nList.item(temp);
                    if (nNode.getNodeType() == Node.ELEMENT_NODE) {

                        Element eElement = (Element) nNode;
                        result = getTagValue("coordinates", eElement);

                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();

        }
        return result;
    }

    // Read New KML
    public String getCoordinateValueFromSavedFile(File fXmlFile) {

        return getCoordinateValue(fXmlFile);

    }


    //TODO : to be review
    public List<Double> getOnlyLatLonValueFromSavedFile(String point) {
        // String resultLatLon = "";=
        List<Double> latlonList = new ArrayList<Double>();
        try {

            StringTokenizer stringTokenizer = new StringTokenizer(point);
            String lat = "";
            String lon = "";
            while (stringTokenizer.hasMoreTokens()) {
                GeoPoint geoPoint = getLatLon(stringTokenizer);

                if (geoPoint != null) {
                    lat = geoPoint.getLatitude();
                    lon = geoPoint.getLongitude();
                    if (lat.length() > 0 && lon.length() > 0) {
                        latlonList.add(Double.parseDouble(lat));
                        latlonList.add(Double.parseDouble(lon));
                    }
                }

                lat = "";
                lon = "";
            }


        } catch (Exception e) {
            latlonList = null;
            e.printStackTrace();

        }
        return latlonList;
    }

    public String getOnlyLatLonValue(String point) {

        String latLonStr = "";
        try {

            StringTokenizer stringTokenizer = new StringTokenizer(point);
            String lat = "";
            String lon = "";
            while (stringTokenizer.hasMoreTokens()) {

                GeoPoint geoPoint = getLatLon(stringTokenizer);

                if (geoPoint != null) {
                    lat = geoPoint.getLatitude();
                    lon = geoPoint.getLongitude();
                    if (lat.length() > 0 && lon.length() > 0) {
                        latLonStr = latLonStr + lat + "," + lon + ",";
                    }
                }

                lat = "";
                lon = "";
            }

        } catch (Exception e) {

            latLonStr = null;
            e.printStackTrace();

        }
        return latLonStr;
    }

    private GeoPoint getLatLon(StringTokenizer stringTokenizer) {
        int arrayCount = 0;
        String firstToken = stringTokenizer.nextToken();
        GeoPoint geoPoint = new GeoPoint();

        StringTokenizer stringTokenizer2 = new StringTokenizer(
                firstToken, ",");

        while (stringTokenizer2.hasMoreTokens()) {
            int col = arrayCount % 3;
            //int row = arrayCount / 3;

            String secondToken = stringTokenizer2.nextToken();
            arrayCount++;

            if (col == 0) {
                //lat = secondToken;
                geoPoint.setLatitude(secondToken);
            } else if (col == 1) {
                //lon = secondToken;
                geoPoint.setLongitude(secondToken);
            }

        }

        return geoPoint;

    }


    //get midPoint
    public GeoParcelCodeDetail getMiddlePoint(String point) {
        String resultMiddle = "";
        GeoParcelCodeDetail parcelCodeDetail = new GeoParcelCodeDetail();
        try {

            StringTokenizer stringTokenizer = new StringTokenizer(point);

            double array[][] = new double[stringTokenizer.countTokens()][3];
            int arrayCount = 0;
            int tokenCount = stringTokenizer.countTokens();
            double lat = 0;
            double lon = 0;
            float alt = 0;
            while (stringTokenizer.hasMoreTokens()) {
                String firstToken = stringTokenizer.nextToken();

                StringTokenizer stringTokenizer2 = new StringTokenizer(
                        firstToken, ",");

                while (stringTokenizer2.hasMoreTokens()) {
                    int col = arrayCount % 3;
                    int row = arrayCount / 3;

                    String secondToken = stringTokenizer2.nextToken();
                    array[row][col] = Double.parseDouble(secondToken);
                    arrayCount++;

                    if (col == 0) {
                        lat = lat + Double.parseDouble(secondToken);
                    } else if (col == 1) {
                        lon = lon + Double.parseDouble(secondToken);
                    } else if (col == 2) {
                        alt = alt + Float.parseFloat(secondToken);
                    }

                }

            }
            resultMiddle = lat / tokenCount + "," + lon / tokenCount + ","
                    + alt / tokenCount;

            parcelCodeDetail.setLatitude(lat / tokenCount);
            parcelCodeDetail.setLongitude(lon / tokenCount);
            parcelCodeDetail.setAltitude(alt / tokenCount);

            //set the reference number
            if (lat != 0 && lon != 0 && alt != 0) {
                long ref = getKMLrefNo((lat / tokenCount), (lon / tokenCount), (alt / tokenCount));
                if (ref != 0) {
                    setReferenceNo(ref);
                } else {
                    setReferenceNo(0);
                }
            } else {
                setReferenceNo(0);
            }

        } catch (Exception e) {
            e.printStackTrace();

        }
        //return resultMiddle;
        return parcelCodeDetail;

    }

    //set Reference Number
    private long getKMLrefNo(double lat, double lon, double alt) {
        long refNo = 0;

        String latitude = tokernizeValue("" + Math.abs(lat));
        if (latitude.length() > 0) {

            String longitude = tokernizeValue("" + Math.abs(lon));

            if (longitude.length() > 0) {

                //String altitude = tokernizeValue("" + Math.abs(alt));
                logger.debug("Lat po {}", latitude.substring(1, 8));
                logger.debug("Lan po {}", longitude.substring(0, 7));
                refNo = Long.parseLong(latitude.substring(1, 8) + longitude.substring(0, 7));
                logger.debug("RefNo on kml {}", refNo);


            } else {
                refNo = 0;
            }

        } else {
            refNo = 0;
        }
        return refNo;

    }

    private String tokernizeValue(String value) {
        String valueOne = "";
        String valueTwo = "";

        StringTokenizer stringTokenizer = new StringTokenizer(value, ".");
        while (stringTokenizer.hasMoreTokens()) {
            valueOne = stringTokenizer.nextToken();
            valueTwo = stringTokenizer.nextToken();
        }
        return valueOne + valueTwo;

    }

    // Write KML from coordinate
    public String writeKmlFileFromCoordinate(String resultCoordinates) {
        // File createFile = null;
        String writerString;
        try {

            DocumentBuilderFactory docFactory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

            // root elements
            Document doc = docBuilder.newDocument();
            Element rootElement = doc.createElement("kml");
            doc.appendChild(rootElement);

            // placemark elements
            Element placemark = doc.createElement("Placemark");
            rootElement.appendChild(placemark);

            // name elements
            Element name = doc.createElement("name");
            name.appendChild(doc.createTextNode("Land"));
            placemark.appendChild(name);

            // Polygon elements
            Element polygon = doc.createElement("Polygon");
            placemark.appendChild(polygon);

            // extrude elements
            Element extrude = doc.createElement("extrude");
            extrude.appendChild(doc.createTextNode("1"));
            polygon.appendChild(extrude);

            // altitudeMode elements
            Element altitudeMode = doc.createElement("altitudeMode");
            altitudeMode.appendChild(doc.createTextNode("relativeToGround"));
            polygon.appendChild(altitudeMode);

            // outerBoundaryIs elements
            Element outerBoundaryIs = doc.createElement("outerBoundaryIs");
            polygon.appendChild(outerBoundaryIs);

            // LinearRing elements
            Element LinearRing = doc.createElement("LinearRing");
            outerBoundaryIs.appendChild(LinearRing);

            /*Element LinearRing = doc.createElement("LineString");
               outerBoundaryIs.appendChild(LinearRing);*/

            // coordinates elements
            Element coordinates = doc.createElement("coordinates");
            // ReadXMLFile readXMLFile = new ReadXMLFile();


            String resultPoint = "";
            if (resultCoordinates != null) {
                //resultPoint = getMiddlePoint(resultCoordinates);

                GeoParcelCodeDetail middlePoint = getMiddlePoint(resultCoordinates);

             resultPoint = middlePoint.getLatitude() + "," + middlePoint.getLongitude() + "," +
                    middlePoint.getAltitude();



                if (resultCoordinates.length() > 0) {
                    coordinates.appendChild(doc
                            .createTextNode(resultCoordinates));

                }

            }

            LinearRing.appendChild(coordinates);

            // Add a point
            // placemark elements
            Element placemark2 = doc.createElement("Placemark");
            rootElement.appendChild(placemark2);

            // name elements
            Element name2 = doc.createElement("name");
            name2.appendChild(doc.createTextNode("" + getReferenceNo()));
            placemark2.appendChild(name2);

            // description elements
            Element description = doc.createElement("description");
            description.appendChild(doc.createTextNode("Middle of the Land"));
            placemark2.appendChild(description);

            // Point elements
            Element Point = doc.createElement("Point");
            placemark2.appendChild(Point);

            // coordinates elements to point
            Element coordinatesPoint = doc.createElement("coordinates");
             //String resultPoint = getMiddlePoint(resultCoordinates);

            if (resultCoordinates != null) {
                if (resultCoordinates.length() > 0) {
                    coordinatesPoint.appendChild(doc.createTextNode(resultPoint));
                }
            }

            Point.appendChild(coordinatesPoint);

            // referenceNo elements
            Element referenceNoEle = doc.createElement("referenceNo");
            if (getReferenceNo() != 0) {
                referenceNoEle.appendChild(doc.createTextNode(""
                        + getReferenceNo()));
            }
            Point.appendChild(referenceNoEle);

            // xml to string
            DOMSource domSource = new DOMSource(doc);
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(domSource, result);
            writerString = writer.toString();

            logger.debug("KmlWriter", "xml to String is success");

        } catch (ParserConfigurationException pce) {
            writerString = null;
            logger.debug("ParserConfigurationException", pce.getMessage());

        } catch (TransformerException tfe) {
            writerString = null;
            logger.debug("TransformerException", tfe.getMessage());
        }

        return writerString;
    }

    private static String getTagValue(String sTag, Element eElement) {
        NodeList nlList = eElement.getElementsByTagName(sTag).item(0)
                .getChildNodes();

        Node nValue = (Node) nlList.item(0);

        return nValue.getNodeValue();
    }

    public long getReferenceNo() {
        return referenceNo;
    }

    public void setReferenceNo(long referenceNo) {
        this.referenceNo = referenceNo;
    }
}


